/*
 * Copyright (c) 2012 Banaanvraag
 * MIT Licence
 */
package banaanvraag.tools.btm;

import banaanvraag.tools.arrays.ByteArray;
import banaanvraag.tools.exceptions.InvalidHeaderException;
import banaanvraag.tools.interfaces.Byteable;
import banaanvraag.tools.lists.ByteArrayList;
import java.util.Arrays;

/**
 * Standard QTM v1 based on BTM v1
 * @author Luc
 */
public class QTM{
    
    /**
     * A QTM class should only support its own version.
     */
    private static final byte version  = 1;
    
    /**
     * The general header of a QTM that will always be the first six bytes, independently of the version.
     */
    private static final byte[] superHeader = new byte[]
    {(byte)0x86, (byte)'Q', (byte)'T', (byte)'M', version, (byte)0x1A};
    
    private int sizeX, sizeY, sizeZ;
    
    private Gridbit[] gridbits;
    
    /**
     * Create a new Cubic Tile Map (but Q because Q is awesome).
     * @param sX
     * @param sY
     * @param sZ 
     */
    public QTM(int sX, int sY, int sZ) {
        sizeX = sX;
        sizeY = sY;
        sizeZ = sZ;
    }
    
    /**
     * Get the width of the map.
     * @return The width (x-axis)
     */
    public int getSizeX() {
        return sizeX;
    }
    
    /**
     * Get the height of the map.
     * @return The height (y-axis)
     */
    public int getSizeY() {
        return sizeY;
    }
    
    /**
     * Get the depth of the map.
     * @return The depth (z-axis)
     */
    public int getSizeZ() {
        return sizeZ;
    }
    
    /**
     * Place a gridbit on a specific location.
     * @param x The x-location
     * @param y The y-location
     * @param z The z-location
     * @param g The gridbit to place
     */
    public void put(int x, int y, int z, Gridbit g) {
        int location = ((y * sizeX + x) * sizeZ + z);
        gridbits[location] = g;
    }
    
    /**
     * Place a gridbit on a specific location by putting it directly in the array.
     * @param location The array index
     * @param g The gridbit to place
     */
    public void put(int location, Gridbit g) {
        gridbits[location] = g;
    }
    
    /**
     * Get a gridbit from a specific location.
     * @param x The x-coordinate
     * @param y The y-coordinate
     * @param z The z-coordinate
     * @return The gridbit that's on that specific location
     */
    public Gridbit get(int x, int y, int z) {
        int location = ((y * sizeX + x) * sizeZ + z);
        return gridbits[location];
    }
    
    /**
     * Get a gridbit from a specific location by directly accessing the array.
     * @param location The array index of the gridbit.
     * @return The gridbit that's in there.
     */
    public Gridbit get(int location) {
        return gridbits[location];
    }
    
    /**
     * Put all data in a byte array, a binary-friendly form.
     * @return The byte array that contains everything a QTM needs.
     */
    public byte[] tobyteArray() {
        ByteArrayList ret = new ByteArrayList();
        
        // Add the header (18 bytes)
        ret.addArray(superHeader);
        ret.addArray(ByteArray.toArray(sizeX));
        ret.addArray(ByteArray.toArray(sizeY));
        ret.addArray(ByteArray.toArray(sizeZ));
        
        // Add the gridbits
        int i = 0;
        while(i < sizeX * sizeY * sizeZ) {
            ret.addArray(ByteArray.toArray(gridbits[i].getType()));
            i++;
        }
        i = 0;
        while(i < sizeX * sizeY * sizeZ) {
            ret.addArray(ByteArray.toArray(gridbits[i].getMeta()));
            i++;
        }
        
        return ret.toByteArray();
    }
    
    /**
     * Take data from a byte array and transform that into a complete QTM
     * @param array The byte array
     * @return The QTM packed with all the data from the array
     * @throws InvalidHeaderException When the header doesn't lign up with this version or if it just isn't a QTM.
     */
    public static QTM FromByteArray(byte[] array) throws InvalidHeaderException {
        ByteArrayList inf = new ByteArrayList();
        inf.addArray(array);
        
        // Check if the header is correct
        byte[] pSH = ByteArray.limitArray(array, 6);
        if(!Arrays.equals(pSH, superHeader)) {
            throw new InvalidHeaderException();
        }
        
        // Determine the size of the map
        byte[] xA = new byte[4];
        xA[0] = inf.get(6);
        xA[1] = inf.get(7);
        xA[2] = inf.get(8);
        xA[3] = inf.get(9);
        
        int sX = ByteArray.toInt(xA);
        
        byte[] yA = new byte[4];
        yA[0] = inf.get(10);
        yA[1] = inf.get(11);
        yA[2] = inf.get(12);
        yA[3] = inf.get(13);
        
        int sY = ByteArray.toInt(yA);
        
        byte[] zA = new byte[4];
        zA[0] = inf.get(14);
        zA[1] = inf.get(15);
        zA[2] = inf.get(16);
        zA[3] = inf.get(17);
        
        int sZ = ByteArray.toInt(zA);
        
        QTM qtm = new QTM(sX, sY, sZ);
        
        int i = 14;
        byte[] tA;
        int t;
        while(i < (4 * sX * sY * sZ) + 18) {
            tA = new byte[4];
            tA[0] = inf.get(i);
            tA[1] = inf.get(i + 1);
            tA[2] = inf.get(i + 2);
            tA[3] = inf.get(i + 3);
            
            t = ByteArray.toInt(tA);
            
            qtm.put((i - 18) / 4, new Gridbit(t, 0));
            
            i += 4;
        }
        
        byte[] mA;
        int m;
        while(i < (8 * sX * sY * sZ) + 18) {
            mA = new byte[4];
            mA[0] = inf.get(i);
            mA[1] = inf.get(i + 1);
            mA[2] = inf.get(i + 2);
            mA[3] = inf.get(i + 3);
            
            m = ByteArray.toInt(mA);
            
            qtm.get((i - 18) / 8).setMeta(m);
            
            i += 4;
        }
        
        return qtm;
    }
}
